package plgloader

import (
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/gobase/factory"
	"gitee.com/ymofen/gobase/gotask"
	"gitee.com/ymofen/gobase/gotask/grouptask"
	"gitee.com/ymofen/golog"
	"gitee.com/ymofen/goplugs/pkg/base"
	"gitee.com/ymofen/supervalue"
	"sync/atomic"
	"time"
)

/*
兼容.db
*/
type PlgsLoader struct {
	base.PlgBase
	PlugId        string
	workflag      int32
	last_reload_t time.Time

	minInterval time.Duration

	startuptime time.Time

	firstdelay time.Duration

	varStartStr, varEndStr           string
	varTokenStartStr, varTokenEndStr string

	conv *base.ConverterWrapper

	fromRef *RefObjectWrapper

	plgloader *base.PlgBaseLoader

	reloadmsg string
	lastConf  string

	templ string
}

func (this *PlgsLoader) onInputConfig(buf []byte) {
	this.lastConf = string(buf)

	newBuf, err := this.conv.InputBuf(buf)
	if err != nil {
		this.Log.Warnf("convert err:%s, config:%s", err.Error(), string(buf))
		return
	}

	json, err := wrapper.NewSVFromBuf([]byte(newBuf))
	if err != nil {
		this.Log.Warnf("conf is invalid err:%s, config:%s", err.Error(), string(newBuf))
		return
	}

	confN, closeN, loadN := 0, 0, 0

	var lst *wrapper.SuperValue
	if json.IsArray() {
		lst = json
	} else {
		lst = json.ValueByName("list")
	}
	if lst == nil {
		return
	}

	this.plgloader.BeginUpdate()
	defer func() {
		closeN = this.plgloader.EndUpdate()
		this.Log.Infof("confN:%d, loadN:%d,closeN:%d", confN, closeN, loadN)
	}()

	lst.Range(func(key string, val *wrapper.SuperValue) bool {
		plgText := this.templ
		if len(plgText) == 0 {
			plgText = val.String()
			plgText = base.ReplacePlaceholder(plgText, this.varStartStr, this.varEndStr, func(key, start, end string) (repl, v string, ok bool) {
				return base.TryGetKeyValue4ReplacePlaceholder(key, start, end, nil)
			})
			if len(this.varTokenStartStr) > 0 && len(this.varTokenEndStr) > 0 {
				plgText = gobase.ReplacePlaceholder(plgText, this.varTokenStartStr, this.varTokenEndStr, func(key string) (v string, ok bool) {
					return gobase.ParseSimpleExp(key)
				})
			}
		} else {
			plgText = base.ReplacePlaceholder(plgText, this.varStartStr, this.varEndStr, func(key, start, end string) (repl, v string, ok bool) {
				return base.TryGetKeyValue4ReplacePlaceholder(key, start, end, func(key string) (v string, ok bool) {
					itm := val.ValueByName(key)
					if itm == nil {
						return "", false
					} else {
						return itm.String(), true
					}
				})
			})
			if len(this.varTokenStartStr) > 0 && len(this.varTokenEndStr) > 0 {
				plgText = gobase.ReplacePlaceholder(plgText, this.varTokenStartStr, this.varTokenEndStr, func(key string) (v string, ok bool) {
					return gobase.ParseSimpleExp(key)
				})
			}
		}
		confN0, loadN0 := this.plgloader.ReloadJSONStr(plgText, func(err error) {
			this.Log.Warnf("%s", err.Error())
		})
		confN += confN0
		loadN += loadN0
		return true
	})
}

func GetAsBytes(v interface{}) []byte {
	if buf, ok := v.([]byte); ok {
		return buf
	}
	if str, ok := v.(string); ok {
		return []byte(str)
	}
	return nil
}

func (this *PlgsLoader) innerDo() {
	logger := this.Log
	if logger == nil {
		return
	}
	if !atomic.CompareAndSwapInt32(&this.workflag, 0, 1) {
		logger.Warnf("The last loading has not been completed yet.")
		return
	}

	logdetail := time.Since(this.startuptime).Seconds() < 10
	if logdetail {
		logger.Infof("start loading")
	}
	t0 := time.Now()
	var confN, loadN, closeN int

	var lastError error

	defer func() {
		this.workflag = 0
		this.last_reload_t = time.Now()
		if lastError != nil {
			this.reloadmsg = fmt.Sprintf("%s loading completed, consume:%d(ms), %s", gobase.NowString(), time.Since(t0).Milliseconds(), lastError.Error())
			logger.Infof("loading completed, consume:%d(ms), %s", time.Since(t0).Milliseconds(), lastError.Error())
		} else {
			this.reloadmsg = fmt.Sprintf("%s loading completed, consume:%d(ms), config:%d, load:%d, closed:%d", gobase.NowString(), time.Since(t0).Milliseconds(), confN, loadN, closeN)
			if logdetail {
				logger.Infof("loading completed, consume:%d(ms), config:%d, load:%d, closed:%d", time.Since(t0).Milliseconds(), confN, loadN, closeN)
			}
		}
	}()

	data, err := this.fromRef.ReadValue()
	if err != nil {
		lastError = fmt.Errorf("loading occur error %s", err.Error())
		return
	}
	buf := GetAsBytes(data)
	strConf := string(buf)
	if strConf != this.lastConf {
		this.onInputConfig(buf)
	}

}

func (this *PlgsLoader) OnCreate() {
	this.PlgBase.OnCreate()
	this.Log = golog.Default().WithOptionEx(golog.WithOptionPrefix(this.PlugId))
}

func (this *PlgsLoader) Close() error {
	this.PlgBase.ExecuteAllAndClose()
	this.fromRef.Close()
	this.plgloader.Close()
	this.PlgBase.Log = nil
	return nil
}

func (this *PlgsLoader) ReloadConf(conf *wrapper.SuperValue) error {
	if conf == nil {
		return nil
	}

	if this.plgloader == nil {
		this.plgloader = base.NewPlgLoader()
	}

	if this.startuptime.IsZero() {
		this.startuptime = time.Now()
	}

	this.varStartStr = conf.StringByName("varStart", "$$")
	this.varEndStr = conf.StringByName("varEnd", "$$")

	this.varTokenStartStr = conf.StringByName("varTokenStart", "${")
	this.varTokenEndStr = conf.StringByName("varTokenEnd", "}$")

	if this.fromRef == nil {
		this.fromRef = NewRefObjectWrapper()
	}

	flag, err := this.fromRef.UpdateConfig(conf.StringByName("from", ""))
	if err != nil {
		this.Log.Warnf("fromRef.UpdateConfig: %s", err.Error())
	} else if flag {
		this.fromRef.TryAddLs(func(topic string, args ...interface{}) {
			grouptask.DefaultGroupTask().PostTaskFunc(this, func() {
				buf := GetAsBytes(args[0])
				this.Log.Infof("recv config, %s", string(buf))
				this.onInputConfig(buf)
			})
		})
	}

	if this.conv == nil {
		this.conv = base.NewConverterWrapper()
	}
	this.conv.UpdateConf(conf.StringByName("converter", ""))

	minInterval := gobase.ParseDurationEx(conf.StringByName("interval", ""), 0)
	this.minInterval = minInterval
	if minInterval > 0 {
		gotask.DefaultPatrolTask().AddTask(this, minInterval, func(id interface{}, args ...interface{}) {
			grouptask.DefaultGroupTask().PostTaskFunc(this, func() {
				if this.minInterval > 0 {
					if !this.last_reload_t.IsZero() && time.Since(this.last_reload_t) < this.minInterval {
						return
					}
				}
				this.innerDo()
			})
		})
	}

	loaddelay := time.Duration(conf.Int64ByName("loadDelaySecs", 20)) * time.Second
	if loaddelay <= 0 {
		this.innerDo()
	} else {
		time.AfterFunc(loaddelay, func() {
			grouptask.DefaultGroupTask().PostTaskFunc(this, func() {
				if this.minInterval > 0 {
					if !this.last_reload_t.IsZero() && time.Since(this.last_reload_t) < this.minInterval {
						return
					}
				}
				this.innerDo()
			})
		})
	}

	this.templ = conf.StringByName("templ", "")
	return nil
}

func CreatePlgsLoaderFunc(args ...interface{}) (rval interface{}, err error) {
	obj := &PlgsLoader{}
	if len(args) > 0 {
		if str, ok := args[0].(string); ok {
			obj.PlugId = str
		}
	}

	rval = obj
	return
}

func init() {
	factory.RegisterCreateFunc("plgs.loader.v2", CreatePlgsLoaderFunc)
}
